package git.soulbgm.common;

import git.soulbgm.utils.ByteUtil;
import git.soulbgm.utils.LogUtil;
import git.soulbgm.utils.SerializeUtil;
import git.soulbgm.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 贺瑞杰
 * @version V1.0
 * @data 2018年4月21日 下午11:00:29
 * @Description Redis操作类  包括String、Hash、List、Set、ZSet
 */
@Component("redisOperate")
public class RedisOperate implements RedisClient {

    private static final String NX = "NX";
    private static final String EX = "EX";

    private final LogUtil log = new LogUtil(getClass());

	/*@Autowired
	private JedisCluster jedisCluster;*/

	/*@Autowired
    private JedisPool jedisPool;*/

    /*@Autowired
    private JedisSentinelPool jedisSentinelPool;*/

    /*@Autowired
    private StringRedisTemplate stringRedisTemplate;*/

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 查看key的剩余生存时间
     * 实现命令：ttl key
     *
     * @param key
     * @return 以毫秒为单位，返回给定key的剩余生存时间
     */
    @Override
    public long ttl(String key) {
        return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
    }

    /**
     * 设置过期时间（单位秒）
     * Redis实现命令：expire key 过期时间（单位秒）
     *
     * @param key
     * @param timeout 过期时间
     */
    @Override
    public boolean expire(String key, long timeout) {
        return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 递增或递减数值类型key的值  例如：num初始为1  执行此方法 incr(num,5) 那么此时num就为6  递减则为负数 原子性操作
     * 适合统计，高并发生成订单号，秒杀类的业务逻辑等使用
     * Redis实现命令：incr key  (注：没有直接加给定数值的命令  只能每次加1)
     *
     * @param key
     * @param delta 增加给定的数值
     * @return
     */
    @Override
    public long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 删除给定的key
     * Redis实现命令：del key
     *
     * @param key
     * @return 删除成功或失败 成功为true 失败为false
     */
    @Override
    public boolean del(String... key) {
        try {
            redisTemplate.delete(Arrays.asList(key));
            return true;
        } catch (Exception e) {
            log.info("删除失败", e.getMessage());
            return false;
        }
    }

    /**
     * 删除给定的key
     * Redis实现命令：del key
     *
     * @param list
     * @return 删除成功或失败 成功为true 失败为false
     */
    @Override
    public boolean del(Collection<String> list) {
        try {
            redisTemplate.delete(list);
            return true;
        } catch (Exception e) {
            log.info("删除失败", e.getMessage());
            return false;
        }
    }

    /**
     * 查找所有符合给定模式的key
     * Redis实现命令：keys 正则表达式   例如：keys na*
     *
     * @param pattern 模式
     * @return 符合模式是所有key
     */
    @Override
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 查找是否有此key 有返回true 没有返回false
     * Redis实现命令：exists key
     *
     * @param key
     * @return
     */
    @Override
    public boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    //TODO K-V 的操作

    /**
     * 设置一个key-value（将字符串值 value关联到 key）
     * Redis实现命令：set key value
     *
     * @param key
     * @param value
     */
    @Override
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置一个key-value（将字符串值 value关联到 key）  序列化方式
     * Redis实现命令：set key value
     *
     * @param key
     * @param data
     * @param clazz
     */
    @Override
    public <T> void set(String key, T data, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            byte[] serializeData = SerializeUtil.serializer(data);
            connection.set(ByteUtil.stringToByteArray(key), serializeData);
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 设置key-value和超时时间（秒）
     * Redis实现命令：set key value ex 超时时间（单位秒）
     *
     * @param key
     * @param value
     * @param timeout
     */
    @Override
    public void set(String key, String value, long timeout) {
        set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置key-value和超时时间（秒）
     * Redis实现命令：set key value ex 超时时间
     *
     * @param key
     * @param value
     * @param timeout
     */
    @Override
    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置key-value和超时时间（秒）  序列化方式
     * Redis实现命令：set key value ex 超时时间（单位秒）
     *
     * @param key
     * @param data
     * @param clazz
     * @param timeout
     */
    @Override
    public <T> void set(String key, T data, Class<T> clazz, long timeout) {
        try {
            set(key, data, clazz);
            expire(key, timeout);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * setnx的工作方式与set完全一样，唯一的区别是，如果键已经存在返回false，则不执行任何操作。 setnx实际上意味着“如果不存在set”
     * Redis实现命令：setnx key value 返回1 set成功 返回0 set失败
     *
     * @param key
     * @param value
     */
    @Override
    public boolean setnx(String key, String value) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            return connection.setNX(ByteUtil.stringToByteArray(key), ByteUtil.toByteArray(value));
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 这个命令类似于以下两个命令
     * set key value
     * expire key seconds  # 设置生存时间
     * 不同之处是，setnx 是一个原子性(atomic)操作，关联值和设置生存时间两个动作会在同一时间内完成，该命令在 Redis 用作缓存时，非常实用
     * key 已经存在时，setex 覆盖旧值
     * Redis实现命令：setex key value 过期时间（单位秒）
     *
     * @param key
     * @param value
     */
    @Override
    public boolean setex(String key, String value, long timeout) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            connection.setEx(ByteUtil.stringToByteArray(key), timeout, ByteUtil.toByteArray(value));
            return true;
        } catch (Exception e) {
            log.error("执行setex方法发生异常", e);
            return false;
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * Redis 中实现锁的简单方法
     * Redis实现命令：set key value NX EX 过期时间（单位秒）
     *
     * @param key
     * @param value
     * @param timeout
     * @return 如果服务器返回 OK ，那么这个客户端获得锁，如果服务器返回 NIL ，那么客户端获取锁失败，可以在稍后再重试。
     */
    @Override
    public String setNxEx(String key, String value, long timeout) {
        return (String) redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                String result = null;
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    result = ((JedisCluster) nativeConnection).set(ByteUtil.stringToByteArray(key), ByteUtil.stringToByteArray(value), ByteUtil.stringToByteArray(NX), ByteUtil.stringToByteArray(EX), timeout);
                }
                // 单机模式
                if (nativeConnection instanceof Jedis) {
                    result = ((Jedis) nativeConnection).set(ByteUtil.stringToByteArray(key), ByteUtil.stringToByteArray(value), ByteUtil.stringToByteArray(NX), ByteUtil.stringToByteArray(EX), timeout);
                }

                return result;
            }
        });
    }

    /**
     * 返回 key所关联的字符串值
     * Redis实现命令：get key
     *
     * @param key
     * @return key对应的value
     */
    @Override
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key所关联的字符串值 序列化方式
     * Redis实现命令：get key
     *
     * @param key
     * @return key对应的value
     */
    @Override
    public <T> T get(String key, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            byte[] data = connection.get(ByteUtil.stringToByteArray(key));
            if (data != null) {
                return SerializeUtil.deserializer(data, clazz);
            }
            return null;
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 返回 key所关联的字符串值 并set进一个新值 属于一个原子性操作
     * Redis实现命令：getset key value		注：如果原本没有key则(nil)
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public Object getAndSet(String key, String value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    //TODO Hash 的操作

    /**
     * 将哈希表 key中的域 field的值设为 value
     * Redis实现命令：hset key field value
     *
     * @param key   哈希表的key
     * @param field 哈希表中的key
     * @param value 哈希表中的key对于的value
     */
    @Override
    public void hset(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 将整个map放哈希表中
     *
     * @param key 哈希表的key
     * @param map Map
     */
    @Override
    public void hsetAll(String key, Map map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 返回哈希表 key中，所有的field和value
     * Redis实现命令：hgetall key
     *
     * @param key
     * @return
     */
    @Override
    public <T> Map<String, T> hgetAll(String key, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        Map<String, T> map = null;
        try {
            Map<byte[], byte[]> data = connection.hGetAll(ByteUtil.stringToByteArray(key));
            if (data != null) {
                map = new HashMap<>();
                Set<byte[]> keySet = data.keySet();
                for (byte[] keyByte : keySet) {
                    T t = SerializeUtil.deserializer(data.get(keyByte), clazz);
                    map.put(StringUtil.byteArrayToString(keyByte), t);
                }
            }
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
        return map;
    }

    /**
     * 返回哈希表 key中给定域 field的值
     * Redis实现命令：hget key field
     *
     * @param key
     * @param field
     * @return 给定域 field的值
     */
    @Override
    public Object hget(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 返回哈希表 key中，所有的field和value
     * Redis实现命令：hgetall key
     *
     * @param key
     * @return
     */
    @Override
    public Map<Object, Object> hgetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 删除哈希表key中的一个会多个指定域，不存在的域将被忽略
     * Redis实现命令：hdel key field [field ...]
     *
     * @param key
     * @param field
     * @return 删除field的数量
     */
    @Override
    public long hdel(String key, Object... field) {
        return redisTemplate.opsForHash().delete(key, field);
    }

    //TODO List 的操作

    /**
     * 将一个值 value插入到列表 key的头部
     * Redis实现命令：lpush key value
     *
     * @param key
     * @param value
     * @return 执行 lpush命令后，列表的长度
     */
    @Override
    public long lpush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 将一个值 value插入到列表 key的头部 序列化方式 保证该list中所有的数据属于同一个类
     * Redis实现命令：lpush key value
     *
     * @param key
     * @param data
     * @param clazz
     * @param <T>
     * @return
     */
    @Override
    public <T> long lpush(String key, T data, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            byte[] serializeData = SerializeUtil.serializer(data);
            return connection.lPush(ByteUtil.stringToByteArray(key), serializeData);
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 将一个值 value插入到列表 key的尾部
     * Redis实现命令：rpush key value
     *
     * @param key
     * @param value
     * @return 执行 rpush命令后，列表的长度
     */
    @Override
    public long rpush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 将一个值 value插入到列表 key的尾部 序列化方式 保证该list中所有的数据属于同一个类
     * Redis实现命令：rpush key value
     *
     * @param key
     * @param data
     * @param clazz
     * @return 执行 rpush命令后，列表的长度
     */
    @Override
    public <T> long rpush(String key, T data, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            byte[] serializeData = SerializeUtil.serializer(data);
            return connection.rPush(ByteUtil.stringToByteArray(key), serializeData);
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 获取从 开始位置到结束位置 的value
     * Redis实现命令：lrange key start end
     *
     * @param key
     * @param start 开始位置
     * @param end   结束位置
     * @return value列表
     */
    @Override
    public List<String> lget(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取从 开始位置到结束位置 的value  序列化方式 保证该list中所有的数据属于同一个类
     * Redis实现命令：lrange key start end
     *
     * @param key
     * @param start 开始位置
     * @param end   结束位置
     * @return value列表
     */
    @Override
    public <T> List<T> lget(String key, long start, long end, Class<T> clazz) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            List<byte[]> bytes = connection.lRange(ByteUtil.stringToByteArray(key), start, end);
            if (bytes != null) {
                List<T> list = new ArrayList<>();
                for (byte[] b : bytes) {
                    list.add(SerializeUtil.deserializer(b, clazz));
                }
                return list;
            }
            return null;
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 获取全部列表
     * Redis实现命令：lrange key 0 -1
     *
     * @param key
     * @return value列表
     */
    @Override
    public List<String> lget(String key) {
        return lget(key, 0, listSize(key));
    }

    /**
     * 获取全部列表 序列化方式 保证该list中所有的数据属于同一个类
     * Redis实现命令：lrange key 0 -1
     *
     * @param key
     * @return value列表
     */
    @Override
    public <T> List<T> lget(String key, Class<T> clazz) {
        return lget(key, 0, listSize(key), clazz);
    }

    /**
     * 获得列表的大小
     *
     * @param key
     * @return 列表大小
     */
    @Override
    public long listSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    //TODO Set 操作

    /**
     * 添加Set
     * Redis实现命令：sadd key value1 value2 ...
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public long setAdd(String key, String... value) {
        return redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 添加Set
     * Redis实现命令：sadd key value1 value2 ...
     *
     * @param key
     * @param clazz
     * @param value
     * @return
     */
    @Override
    public <T> long setAdd(String key, Class<T> clazz, T value) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            byte[] data = SerializeUtil.serializer(value);
            return connection.sAdd(ByteUtil.stringToByteArray(key), data);
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 删除Set值
     * Redis实现命令：srem key value1 value2 ...
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public long setRemove(String key, Object... value) {
        return redisTemplate.opsForSet().remove(key, value);
    }

    /**
     * 获得Set的所有值
     * Redis实现命令：smembers key
     *
     * @param key
     * @return
     */
    @Override
    public Set<String> setAll(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 获得Set的所有值
     * Redis实现命令：smembers key
     *
     * @param key
     * @return
     */
    @Override
    public <T> Set<T> setAll(String key, Class<T> tClass) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            Set<byte[]> bytes = connection.sMembers(ByteUtil.stringToByteArray(key));
            if (bytes != null) {
                Set<T> set = new HashSet<>();
                for (byte[] b : bytes) {
                    set.add(SerializeUtil.deserializer(b, tClass));
                }
                return set;
            }
            return null;
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 判断Set中是否存在指定的value
     * Redis实现命令：sismember key value
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean setExist(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 返回Set的长度
     * Redis实现命令：scard key  如果set不存在，返回0
     *
     * @param key
     * @return
     */
    @Override
    public long setSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 随机返回Set中的一个数据
     * Redis实现命令：srandmember key
     *
     * @param key
     * @return
     */
    @Override
    public Object setRandom(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    //TODO ZSet 操作

    /**
     * 添加ZSet
     * Redis实现命令：zadd key score value
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean zsetAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 添加ZSet到最上面
     * Redis实现命令：zadd key 负的ZSet的长度 value
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean zsetStartAdd(String key, String value) {
        Long size = zsetSize(key);
        if (size > 0) {
            size = 0 - size;
        }
        return zsetAdd(key, value, size);
    }

    /**
     * 添加ZSet到最下面
     * Redis实现命令：zadd key 大于等于1的数据 value
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public boolean zsetEndAdd(String key, String value) {
        Long size = zsetSize(key);
        return zsetAdd(key, value, size);
    }

    /**
     * 删除ZSet值
     * Redis实现命令：zrem key value1 value2 ...
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public long zsetRemove(String key, Object... value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }

    /**
     * 删除ZSet值
     * Redis实现命令：zrem key value1 value2 ...
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    @Override
    public long zsetRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 获得指定的ZSet的值
     * Redis实现命令：zrange key start end
     *
     * @param key
     * @return
     */
    @Override
    public Set<String> zsetRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 获得ZSet的所有的值
     * Redis实现命令：zrange key 0 -1
     *
     * @param key
     * @return
     */
    @Override
    public Set<String> zsetAll(String key) {
        return zsetRange(key, 0, -1);
    }

    /**
     * 获得Set的所有值
     * Redis实现命令：zrange key start end
     *
     * @param key
     * @return
     */
    @Override
    public <T> Set<T> zsetRange(String key, long start, long end, Class<T> tClass) {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection connection = connectionFactory.getConnection();
        try {
            Set<byte[]> bytes = connection.zRange(ByteUtil.stringToByteArray(key), start, end);
            if (bytes != null) {
                Set<T> set = new HashSet<>();
                for (byte[] b : bytes) {
                    set.add(SerializeUtil.deserializer(b, tClass));
                }
                return set;
            }
            return null;
        } finally {
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
        }
    }

    /**
     * 获得Set的所有值
     * Redis实现命令：zrange key 0 -1
     *
     * @param key
     * @return
     */
    @Override
    public <T> Set<T> zsetAll(String key, Class<T> tClass) {
        return zsetRange(key, 0, -1, tClass);
    }

    /**
     * 返回Set的长度
     * Redis实现命令：scard key  如果set不存在，返回0
     *
     * @param key
     * @return
     */
    @Override
    public long zsetSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }


    /**
     * 获得jedis
     *
     * @return
     *//*
    private Jedis getJedis() {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
        ReflectionUtils.makeAccessible(jedisField);
        Jedis jedis = (Jedis) ReflectionUtils.getField(jedisField, connectionFactory.getConnection());
        return jedis;
    }

    *//**
     * 释放连接
     *
     * @param jedis
     *//*
    private void close(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }*/


    /**
     * 使用Lua脚本调用redis
     *
     * @param script Lua脚本
     * @param keys   脚本中使用的KEYS
     * @param args   脚本中使用的ARGV
     * @param clazz  脚本return出信息的Class
     * @return 脚本中return出的信息
     */
    @Override
    public <T> T eval(String script, Class<T> clazz, List<String> keys, String... args) {

        return (T) redisTemplate.execute(new RedisCallback<T>() {
            @Override
            public T doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                T result = null;
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    result = (T) ((JedisCluster) nativeConnection).eval(script, keys, Arrays.asList(args));
                }
                // 单机模式
                if (nativeConnection instanceof Jedis) {
                    result = (T) ((Jedis) nativeConnection).eval(script, keys, Arrays.asList(args));
                }

                return result;
            }
        });

        /*try {
            RedisScript redisScript = new DefaultRedisScript(script, clazz);
            T obj = (T) redisTemplate.execute(redisScript, keys, args);
            return obj;
        } catch (Exception e) {
            return null;
        }*/
    }

}
